/*
 * Copyright (c) 2007 jMouse Gestures
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMouse Gestures' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmg.gesture;

import com.jmg.gesture.event.GesturePerformedListener;
import com.jmg.mouse.MouseMovement;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;


/**
 * GestureGroup class contains a list of
 * {@link com.jmg.gesture.Gesture} classes and a list of
 * {@link com.jmg.gesture.event.GesturePerformedListener} that are notified
 * if a valid gesture is performed.
 * 
 * @author  Christopher Martin
 * @version $Id: GestureGroup.java 13 2007-05-24 12:13:13Z GentlemanHal $
 */
public class GestureGroup implements
    Serializable
{
    /** Serial Version UID */
    private static final long serialVersionUID = 1L;
    
    private String name;
    private Collection<Gesture> gestures;
    private Collection<GesturePerformedListener> listeners;
    
    
    
    
    
    /** 
     * Creates a new instance of <code>GestureGroup</code> with the name
     * &quot;GestureGroup&quot; and synchronized ArrayLists as the backing
     * collections for gestures and listeners.
     */
    public GestureGroup() {
        this(null);
    }
    
    /**
     * Creates a new instance of <code>GestureGroup</code> with the given
     * name and synchronized ArrayLists as the backing
     * collections for gestures and listeners.  If <code>null</code> is passed 
     * then &quot;GestureGroup&quot; is automatically set as the name.
     *
     * @param name The name of this group.
     */
    public GestureGroup(String name) {
        this(name, new ArrayList<Gesture>(), new ArrayList<GesturePerformedListener>());
    }
    
    /**
     * Creates a new instance of <code>GestureGroup</code>.
     * 
     * @param  name The name of this <code>GestureGroup</code>.  This property
     *         is immutable, once it has been set here it can not be set again.
     * @param  gestures The backing collection for gestures.
     * @param  listeners The backing collection for gesture listeners.
     * @throws java.lang.IllegalArgumentException If a <code>null</code> argument
     *         is passed for either gestures or listeners.
     */
    public GestureGroup(String name, Collection<Gesture> gestures, 
                        Collection<GesturePerformedListener> listeners)
        throws IllegalArgumentException
    {
        if (null == gestures) {
            throw new IllegalArgumentException("gestures backing list was null");
        }
        if (null == listeners) {
            throw new IllegalArgumentException("listeners backing list was null");
        }
        
        
        this.gestures  = gestures;
        this.listeners = listeners;
        
        this.name = null == name ? "GestureGroup" : name;
    }
    
    
    
    
    
    /**
     * Returns the name of this <code>GestureGroup</code> or 
     * <code>&quot;GestureGroup&quot;</code> if this gesture was not named.
     * <p>
     * This method will not return <code>null</code> but it may return an 
     * empty string &quot;&quot;.
     * </p>
     * 
     * @return The name.
     */
    public String getName() {
        return name;
    }
    
    /**
     * Adds a new <code>GesturePerformedListeners</code>, if a <code>null</code>
     * argument is passed then no listener is added and no exception is thrown.
     * 
     * @param l The <code>GesturePerformedListeners</code> to add.
     */
    public void addGesturePerformedListener(GesturePerformedListener l) {
        if (null != l) {
            listeners.add(l);
        }
    }
    
    /**
     * Removes an existing <code>GesturePerformedListeners</code>, if a 
     * <code>null</code> argument is passed or the listener was never added to
     * this instance then no listener is removed and no exception is thrown.
     * 
     * @param l The <code>GesturePerformedListeners</code> to remove.
     */
    public void removeGesturePerformedListener(GesturePerformedListener l) {
        if (null != l) {
            listeners.remove(l);
        }
    }
    
    /**
     * Returns all the registered <code>GesturePerformedListeners</code>.
     * If no listeners have been added then an empty array is returned.
     * 
     * @return The <code>GesturePerformedListeners</code> or an empty array.
     */
    public GesturePerformedListener[] getGesturePerformedListeners() {
        return listeners.toArray(new GesturePerformedListener[listeners.size()]);
    }
    
    /**
     * Notifies all listeners that the given <code>Gesture</code> was performed.
     * If a <code>null</code> argument is passed then no action is taken and no
     * exception is thrown.
     * 
     * @param gesture The <code>Gesture</code> that was performed.
     */
    public void notifyGesturePerformedListeners(Gesture gesture) {
        if (null != gesture) {
            long when = System.currentTimeMillis();
            
            for (GesturePerformedListener l : listeners) {
                l.gesturePerformed(when, gesture);
            }
        }
    }
    
    /**
     * Adds a new <code>Gesture</code>, if a <code>null</code> argument is 
     * passed then no <code>Gesture</code> is added and no exception is thrown.
     * 
     * @param gesture The <code>Gesture</code> to add.
     */
    public void addGesture(Gesture gesture) {
        if (null != gesture) {
            gestures.add(gesture);
        }
    }
    
    /**
     * Removes an existing <code>Gesture</code>, if a <code>null</code>
     * argument is passed or the <code>Gesture</code> was never added to this
     * class then no <code>Gesture</code> is removed and no exception is thrown.
     * 
     * @param gesture The <code>Gesture</code> to remove.
     */
    public void removeGesture(Gesture gesture) {
        if (null != gesture) {
            gestures.remove(gesture);
        }
    }
    
    /**
     * Returns all the registered <code>Gestures</code>.  If no 
     * <code>Gestures</code> have been added then an empty array is returned.
     * 
     * @return The <code>Gestures</code> or an empty array.
     */
    public Gesture[] getGestures() {
        return gestures.toArray(new Gesture[gestures.size()]);
    }
    
    /**
     * Checks all registered <code>Gestures</code> against the given 
     * <code>MouseMovements</code> to see if a gesture was performed.  
     * This method will automatically break on the first <code>Gesture</code>
     * that matches the <code>MouseMovements</code>.
     * <p>
     * If a <code>null</code> argument is passed or an empty array then
     * <code>null</code> is just returned.
     * </p><p>
     * This method will need to be manually synchronised if there is a
     * possibility the gesture collection may be updated by another thread.
     * </p><p>
     * eg:
     * <pre>
     * synchronized (theGroup) {
     *     theGroup.checkForPerformedGesture(movements);
     * }
     * </pre>
     * </p>
     * 
     * @param  movements The <code>MouseMovements</code> to check against.
     * @return The <code>Gesture</code> that was performed or <code>null</code>
     *         if no <code>Gesture</code> was performed.
     */
    public Collection<Gesture> checkForPerformedGesture(MouseMovement ... movements) {
        return checkForPerformedGesture(new ArrayList<Gesture>(), movements);
    }
    
    /**
     * Checks all registered <code>Gestures</code> against the given 
     * <code>MouseMovements</code> to see if a gesture was performed.  
     * This method will automatically break on the first <code>Gesture</code>
     * that matches the <code>MouseMovements</code>.
     * <p>
     * If a <code>null</code> argument is passed or an empty array then
     * <code>null</code> is just returned.
     * </p><p>
     * This method will need to be manually synchronised if there is a
     * possibility the gesture collection may be updated by another thread.
     * </p><p>
     * eg:
     * <pre>
     * synchronized (theGroup) {
     *     theGroup.checkForPerformedGesture(movements);
     * }
     * </pre>
     * </p>
     * 
     * @param  performed The collection that will hold any gestures that were
     *         performed.
     * @param  movements The <code>MouseMovements</code> to check against.
     * @return The <code>Gesture</code> that was performed or <code>null</code>
     *         if no <code>Gesture</code> was performed.
     */
    public Collection<Gesture> checkForPerformedGesture(Collection<Gesture> performed, 
                                                        MouseMovement ... movements) 
    {
        if (null == movements || 0 == movements.length) {
            return performed;
        }
        
        
        for (Gesture gesture : gestures) {
            if (gesture.validGesture(movements)) {
                performed.add(gesture);
            }
        }
        
        return performed;
    }
    
    /**
     * Checks for a performed gesture and notifies any listeners if one is
     * performed.  This is just a convenience method that combines the
     * {@link #checkForPerformedGesture(Collection, MouseMovement[]) checkForPerformedGesture} 
     * method and the 
     * {@link #notifyGesturePerformedListeners notifyGesturePerformedListeners} 
     * method into one call.
     * 
     * @param movements The <code>MouseMovements</code> to check against.
     */
    public void checkAndNotify(MouseMovement ... movements) {
        Collection<Gesture> performed = checkForPerformedGesture(movements);
        
        for (Gesture g : performed) {
            notifyGesturePerformedListeners(g);
        }
    }





    /**
     * Returns a <code>String</code> representation of this 
     * <code>GestureGroup</code>.
     * 
     * @return A <code>String</code> representation.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(50);
        
        buf.append(getClass().getName());
        buf.append("[name=").append(name);
        buf.append(",mappings=").append(gestures);
        buf.append(",listeners=").append(listeners);
        buf.append(']');
        
        return buf.toString();
    }

    /**
     * Indicates whether some other <code>Object</code> is equal to this one.
     * 
     * @param  obj The reference <code>Object</code> with which to compare.
     * @return <code>true</code> if this object is the same as the obj
     *         argument, <code>false</code> otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        
        if (null == obj || obj.getClass() != getClass()) {
            return false;
        }
            
        GestureGroup g = (GestureGroup) obj;

        return g.name.equals(name)         &&
               g.gestures.equals(gestures) && 
               g.listeners.equals(listeners);
    }

    /**
     * Returns a hash code value for the <code>GestureGroup</code>.
     * 
     * @return A hash code value for this <code>GestureGroup</code>.
     */
    @Override
    public int hashCode() {
        return name.hashCode()      * 13 + 
               gestures.hashCode()  * 13 + 
               listeners.hashCode() * 13;
    }
    
}
